/**
 * @file gametester.cpp
 * @brief Game automated tests implementation
 *
 * This file contains implementation of the tests related to the game logic.
 * @sa gamemsgtests.cpp
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 *
 * @author VestniK (Sergey N.Vidyuk) sir.vestnik@gmail.com
 * @date 17 Oct 2008
 */
#include "gametester.h"

#include <QtTest/QtTest>
#include <QtCore/QtDebug>
#include <QtCore/QList>
#include <QtCore/QString>
#include <QtCore/QUuid>

#include "defaults.h"
#include "game.h"
#include "serverplayer.h"
#include "server.h"

#include "enumstostr.h"

Q_DECLARE_METATYPE(mjn::WindType);
Q_DECLARE_METATYPE(mjn::TileID);
Q_DECLARE_METATYPE(QList<mjn::TileID>);

void GameTester::initTestCase() {
   qRegisterMetaType<mjn::WindType>("mjn::WindType");
   qRegisterMetaType<mjn::TileID>("mjn::TileID");
   qRegisterMetaType< QList<mjn::TileID> >("QList<mjn::TileID>");
   qRegisterMetaType< QList<QString> >("QList<QString>");
}

void GameTester::testNewPlayerCreation() {
   Game game;
   for ( int i = 0; i < PLAYERS_COUNT; i++ ) {
      QVERIFY( !game.noSlots() );
      QVERIFY( game.addNewPlayer() != 0 );
      QCOMPARE(game.players().count() , i+1);
   }
   QVERIFY( game.noSlots() );
   game.addNewPlayer();
   QCOMPARE(game.players().count() , PLAYERS_COUNT);
}

void GameTester::testStartGame() {
   Game game;
   QCOMPARE(game.state() , Game::WaitingForPlayers);
   for ( int i = 0; i < PLAYERS_COUNT; i++ ) {
      QVERIFY( !game.start() );
      QCOMPARE(game.state() , Game::WaitingForPlayers);
      game.addNewPlayer();
   }
   QCOMPARE(game.state() , Game::ReadyToStart);
   QVERIFY( game.start() );
   QCOMPARE(game.state() , Game::Started);
}

void GameTester::testPlayerWindRandomization() {
   Game game;
   for ( int i = 0; i < PLAYERS_COUNT; i++ ) {
      game.addNewPlayer();
   }
   QVERIFY(game.start());
   QList<ServerPlayer *> players = game.players();
   for ( int i = 0; i < players.count(); i++ ) {
      for ( int j = i+1; j < players.count(); j++ ) {
         QVERIFY2( players[i]->playerWind() != players[j]->playerWind() ,
                   "All players should be associated with different winds.");
      }
   }
}

void GameTester::testBeginRound() {
   Game game;
   game.wall().buildWall(game.tileSet());
   ServerPlayer p(&game);
   // East
   p.setPlayerWind( East );
   p.beginRound();
   QCOMPARE(p.getHandSize() , HAND_SIZE + 1);
   // South
   p.setPlayerWind( South );
   p.beginRound();
   QCOMPARE(p.getHandSize() , HAND_SIZE);
   // West
   p.setPlayerWind( West );
   p.beginRound();
   QCOMPARE(p.getHandSize() , HAND_SIZE);
   // North
   p.setPlayerWind( North );
   p.beginRound();
   QCOMPARE(p.getHandSize() , HAND_SIZE);
}

void GameTester::testStartTurn() {
   // Some initialisation
   Game game;
   game.wall().buildWall(game.tileSet());
   game.addNewPlayer();
   ServerPlayer *p = game.players().first();
   QSignalSpy startOfTurnSpy(p,SIGNAL(startOfTurn()));
   // Start turn test
   QCOMPARE( startOfTurnSpy.count() , 0 );
   p->makeTurn();
   QVERIFY( p->isMyTurn() );
   QCOMPARE( startOfTurnSpy.count() , 1 );
}

void GameTester::testEndTurn() {
   // Some initialisation
   Game game;
   game.wall().buildWall(game.tileSet());
   ServerPlayer *p = game.addNewPlayer();
   QSignalSpy errorSpy(p,SIGNAL(riseError(QString)));
   // Start turn
   p->beginRound();
   p->makeTurn();
   p->takeTileFromWall();
   int handSize = p->getHandSize();
   // Incorrect stop of turn
   TileID tileToDiscard = 0;
   while ( tileToDiscard < game.tileSet().getTilesCount() &&
           p->getHand().contains(tileToDiscard) ) {
      tileToDiscard ++;
   }
   QCOMPARE( errorSpy.count() , 0 );
   p->endTurn(tileToDiscard);
   QCOMPARE( errorSpy.count() , 1 );
   QVERIFY( p->isMyTurn() );
   QCOMPARE( p->getHandSize() , handSize );
   // Stop turn by discarding first tile from hand
   tileToDiscard = p->getHand().first();
   p->endTurn( tileToDiscard );
   QCOMPARE( p->getHandSize() , handSize-1 );
   QCOMPARE( game.takeDiscardedTile() , tileToDiscard );
   QVERIFY( !p->isMyTurn() );
}

void GameTester::testSwitchTurn() {
   Game game;
   for (int i = 0; i < PLAYERS_COUNT; i++) {
      game.addNewPlayer();
   }
   WindType winds[4];
   winds[0] = East;
   winds[1] = South;
   winds[2] = West;
   winds[3] = North;
   int turn_count = 20; // lets make 20 turns
   for ( int i = 0; i < turn_count; i++ ) {
      foreach( ServerPlayer *p , game.players() ) {
         if ( p->isMyTurn() ) {
            QCOMPARE(p->playerWind() , winds[i%4]);
            if ( ! p->tileTaken() ) {
               p->takeTileFromWall();
            }
            p->endTurn( p->getHand()[0] );
            break;
         }
      }
   }
}

void GameTester::testTakeTile() {
   Game game;
   game.wall().buildWall(game.tileSet());
   game.addNewPlayer();
   ServerPlayer *p = game.players().first();
   QSignalSpy tileTakenSpy(p,SIGNAL(tileTaken(mjn::TileID)));
   connect( p,SIGNAL(tileTaken(mjn::TileID)),
            this,SLOT(catchTile(mjn::TileID)) );
   QSignalSpy errorSpy(p,SIGNAL(riseError(QString)));
   p->makeTurn();
   // Tile hasn't been taken we can tike one
   p->takeTileFromWall();
   QCOMPARE(p->getHand().size() , 1);
   QCOMPARE( tileTakenSpy.count() , 1);
   QCOMPARE( mCatchedTiles.last() , p->getHand().first() );
   // Tile has already been taken we can't take another one.
   QCOMPARE( errorSpy.count() , 0 );
   p->takeTileFromWall();
   QCOMPARE(p->getHand().size() , 1);
   QCOMPARE( errorSpy.count() , 1 );
   // Discard the tile and try to take it in another turn
   errorSpy.clear();
   tileTakenSpy.clear();
   TileID discardedTile = p->getHand().first();
   p->endTurn( discardedTile );
   p->makeTurn();
   p->takeDiscardedTile();
   QCOMPARE(errorSpy.count() , 0);
   QCOMPARE(tileTakenSpy.count() , 1);
   QCOMPARE(mCatchedTiles.last() , discardedTile);
   QCOMPARE(p->getHand().first() , discardedTile);
}

void GameTester::testRoundFailed() {
   Game game;
   for (int i = 0; i < PLAYERS_COUNT; i++) {
      game.addNewPlayer();
   }
   game.start();
   QMap<ServerPlayer* , WindType> PlayersWindsBakup;
   QMap<ServerPlayer* , int> PlayersScoreBakup;
   ServerPlayer *CurrentPlayer;
   // Make first turn
   foreach( ServerPlayer* p , game.players() ) {
      PlayersWindsBakup[p] = p->playerWind();
      PlayersScoreBakup[p] = p->getScore();
      if ( p->isMyTurn() ) {
         CurrentPlayer = p;
      }
   }
   CurrentPlayer->endTurn( CurrentPlayer->getHand()[0] );
   // Emptify the wall
   while ( game.wall().getWallSize() > MINIMUM_WALL_SIZE+1 ) {
      game.wall().takeTile();
   }
   // After this turn the round should fail
   foreach( CurrentPlayer , game.players() ) {
      if ( CurrentPlayer->isMyTurn() ) {
         CurrentPlayer->takeTileFromWall();
         CurrentPlayer->endTurn( CurrentPlayer->getHand()[0] );
         break;
      }
   }
   // Checking that new round was started without shifting the player's winds and score calculation
   foreach( CurrentPlayer , game.players() ) {
      QCOMPARE( CurrentPlayer->getScore() , PlayersScoreBakup[CurrentPlayer] );
      QCOMPARE( CurrentPlayer->playerWind() , PlayersWindsBakup[CurrentPlayer] );
      if ( CurrentPlayer->playerWind() == East ) {
         QVERIFY( CurrentPlayer->isMyTurn() );
         QVERIFY( CurrentPlayer->tileTaken() );
      } else {
         QVERIFY( ! CurrentPlayer->isMyTurn() );
      }
   }
   QCOMPARE( game.wall().getWallSize() , (unsigned)(TILE_COUNT-(DEAD_END_SIZE+HAND_SIZE*PLAYERS_COUNT+1)) );
}

void GameTester::testStartOfRound() {
   // Some initialisation
   Game game;
   game.wall().buildWall(game.tileSet());
   game.setWind(West);
   ServerPlayer p(&game);
   p.setPlayerWind( East );
   QSignalSpy spy( &p,SIGNAL(startOfRound(mjn::WindType, mjn::WindType, QList<mjn::TileID>)) );
   // Test itself
   p.beginRound();
   QCOMPARE(spy.count() , 1);
   QCOMPARE( spy.first().at(0).value<WindType>() , p.playerWind() );
   QCOMPARE( spy.first().at(1).value<WindType>() , game.wind() );
   QCOMPARE( spy.first().at(2).value< QList<TileID> >() , p.getHand() );
}

void GameTester::testSetPlayerName() {
    QString newName = "new name";
    Game game;
    ServerPlayer *player = game.addNewPlayer();
    QSignalSpy spy(player,SIGNAL(updated()));
    QCOMPARE(spy.count() , 0);
    game.players()[0]->setName(newName);
    QCOMPARE(spy.count() , 1);
}

void GameTester::testTileDiscardedSignal() {
   Game game;
   QSignalSpy spy(&game,SIGNAL(tileDiscarded(mjn::TileID)));
   connect(&game,SIGNAL(tileDiscarded(mjn::TileID)),
           this,SLOT(catchTile(mjn::TileID)));
   for (int i = 0; i < PLAYERS_COUNT; i++) {
      game.addNewPlayer();
   }
   game.start();
   ServerPlayer *p;
   foreach( p , game.players() ) {
      if ( p->isMyTurn() ) {
         break;
      }
   }
   QCOMPARE(spy.count() , 0);
   TileID tileToDiscard = p->getHand().first();
   p->endTurn( tileToDiscard );
   QCOMPARE(spy.count() , 1);
   QCOMPARE(mCatchedTiles.last() , tileToDiscard);
}

void GameTester::testRoundFailedSignal() {
   Game game;
   QSignalSpy spy(&game,SIGNAL(roundFailed()));
   for (int i = 0; i < PLAYERS_COUNT; i++) {
      game.addNewPlayer();
   }
   game.start();
   ServerPlayer *p;
   QCOMPARE(spy.count() , 0);
   foreach( p , game.players() ) {
      if ( p->isMyTurn() ) {
         p->endTurn( p->getHand().first() );
         break;
      }
   }
   while ( game.wall().getWallSize() > MINIMUM_WALL_SIZE+1 ) {
      game.wall().takeTile();
   }
   QCOMPARE(spy.count() , 0);
   foreach( p , game.players() ) {
      if ( p->isMyTurn() ) {
         p->takeTileFromWall();
         p->endTurn( p->getHand().first() );
         break;
      }
   }
   QCOMPARE(spy.count() , 1);
}
